-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
No innerHTML and pseudo-html cleanup #1792
Conversation
not a huge deal, but can improve caching
so we don't depend on callers getting this right
because now text nodes (that have been through convertToTspans) are cacheable but larger nodes are generally not.
fallback avoids errors with mathjax in the legend, but it still doesn't always get the sizing right because we're not handling async correctly.
that way nobody needs to know about 'tspan.line' outside of svg_text_utils
hidden elements still contribute to bounding boxes. mostly we are careful to calculate the bbox of the visible one but not in all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great so far 👍
As of the latest commit, a few image tests are failing as well as one updatemenu jasmine test and there's one linting mistake.
Thanks for making svg_text_utils.js
robust and reusable. Not having to think about tspan.line
should speed up development for new components.
|
||
function showText() { | ||
if(!parent.empty()) { | ||
svgClass = _context.attr('class') + '-math'; | ||
parent.select('svg.' + svgClass).remove(); | ||
} | ||
_context.text('') | ||
.style({ | ||
visibility: 'inherit', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Why did this line get 🔪ed ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I didn't notice the original issue #984 that led to this change... assumed it was a remnant of a much older time. My goal was to remove inline styles whenever possible, rather than setting an explicit reset value, but anyway I moved from visibility:hidden
to display:none
because only the latter takes the item out of the bounding box, and there are still a couple of places (most notably titles/index:146 & 171 that I'm still working on) we get the bounding box of the combined text+MathJax element. I'll see if I can come up with a test of #984 to include with this PR.
src/components/updatemenus/draw.js
Outdated
|
||
// width is given by max width of all buttons | ||
var tWidth = text.node() && Drawing.bBox(text.node()).width, | ||
wEff = Math.max(tWidth + constants.textPadX, constants.minWidth); | ||
|
||
// height is determined by item text | ||
var tHeight = menuOpts.font.size * constants.fontSizeToHeight, | ||
tLines = tspans[0].length || 1, | ||
tLines = tspans.size() || 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. My bad for putting these in.
src/components/legend/draw.js
Outdated
textLines = textSpans[0].length || 1; | ||
textSpans = text.selectAll('tspan.line'), | ||
textLines = textSpans.size() || 1, | ||
textNode = text.node(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there always only one <.legendtext>
node per g
? If so, we could replace the
var text = g.selectAll('.legendtext')
line with
var text = g.select('.legendtext')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, that's a bit cleaner -> 27b4be3
src/components/drawing/index.js
Outdated
} | ||
// standardize its position (and newline tspans if any) | ||
d3.select(testNode) | ||
.attr('transform', '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Wouldn't .attr('transform', null)
be d3-esque?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's better -> 82ba7ee
@@ -149,8 +149,6 @@ function getFillColor(selectorLayout, d) { | |||
function drawButtonText(button, selectorLayout, d, gd) { | |||
function textLayout(s) { | |||
svgTextUtils.convertToTspans(s, gd); | |||
|
|||
// TODO do we need anything else here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... I guess not. 😆
src/components/rangeselector/draw.js
Outdated
tHeight = opts.font.size * 1.3, | ||
tLines = svgTextUtils.lineCount(text); | ||
var tWidth = text.node() && Drawing.bBox(text.node()).width; | ||
var tHeight = opts.font.size * 1.3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that 1.3
factor should be put in a constant file.
Maybe in
https://github.com/plotly/plotly.js/blob/master/src/constants/alignment.js
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or maybe inside a new svg_text_utils.js
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. It's used in a few different ways, so I went for alignment.js
-> c5fa44b
// but in a static plot it's useless and it can confuse batik | ||
// we've tried to standardize on display:none but make sure we still | ||
// catch visibility:hidden if it ever arises | ||
if(txt.style('visibility') === 'hidden' || txt.style('display') === 'none') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referencing #990
@@ -214,6 +221,7 @@ function drawAxisTitle(layer, trace, t, xy, dxy, axis, xa, ya, offset, labelClas | |||
var el = d3.select(this); | |||
|
|||
el.text(axis.title || '') | |||
.call(svgTextUtils.convertToTspans, gd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing, Pinging @rreusser
tasks/test_syntax.js
Outdated
logs.push(file + ' : contains .classList (IE failure)'); | ||
} | ||
else if(lastPart === 'innerHTML') { | ||
logs.push(file + ' : contains .innerHTML (IE failure in SVG)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be exceptions to this rule in the future, if we end up using innerHTML
on non-SVG DOM elements. Oh well, we could always filter the cases out then. We might we want to add a comment here signalling this check is too strict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good thought. Comment added -> a68f861
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
d3.select(gd).style('visibility', 'inherit'); | ||
|
||
Plotly.plot(gd, subplotMock.data, subplotMock.layout).then(function() { | ||
|
||
d3.select(gd).selectAll('text').each(function() { | ||
expect(d3.select(this).style('visibility')).toEqual('visible'); | ||
expect(d3.select(this).style('display')).toEqual('block'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice touch. Thanks!
otherwise each tspan is relative to the previous, so the 3rd line `dy=2.6em` goes 2 lines below the 2nd. Alternatively I guess we could put `dy=1.3em` on all of them except the first but this way seems more explicit and robust
and stopped autosizing the mock, which makes it look different in the test dashboard vs the image server
and this is fine because we're calculating them in a totally different way in order to allow for multiline labels
multiline labels are calculating their widths correctly now because the newline tspans are lining up rather than stair stepping
💃 after that linting mistake is fixed.
I suppose this will wait for a future PR? |
I'm going to take a stab at making the ones in |
this must have been broken since day 1, oddly enough...
@@ -214,7 +214,7 @@ function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) { | |||
.classed('user-select-none', true) | |||
.attr('text-anchor', 'end') | |||
.call(Drawing.font, menuOpts.font) | |||
.text('▼'); | |||
.text(constants.arrowSymbol[menuOpts.direction]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 🎉
tasks/test_syntax.js
Outdated
@@ -77,6 +77,9 @@ function assertSrcContents() { | |||
else if(lastPart === 'innerHTML') { | |||
logs.push(file + ' : contains .innerHTML (IE failure in SVG)'); | |||
} | |||
else if(lastPart === 'parentElement') { | |||
logs.push(file + ' : contains .parentElement (IE failure)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference purposes: https://stackoverflow.com/questions/8685739/difference-between-dom-parentnode-and-parentelement
} | ||
}, | ||
// multiple of fontSize to get the vertical offset between lines | ||
LINE_SPACING: 1.3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉 🎉 🎉
@@ -131,7 +131,7 @@ function createCamera(scene) { | |||
|
|||
if(Math.abs(dx * dydx) > Math.abs(dy)) { | |||
result.boxEnd[1] = result.boxStart[1] + | |||
Math.abs(dx) * dydx * (Math.sign(dy) || 1); | |||
Math.abs(dx) * dydx * (dy >= 0 ? 1 : -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Non-blocking comment] It might be worth making a Lib.sign
method at some point.
|
||
// then merge in a few more with other component types | ||
mock.layout.updatemenus = require('@mocks/updatemenus.json').layout.updatemenus; | ||
mock.layout.sliders = require('@mocks/sliders.json').layout.sliders; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea here putting all traces and components in one data/layout
. Maybe we should do the same in the svg/pdf/eps image export tests
Amazing PR. 💃 Text placement was always a tricky part of adding new layout components. This PR will make that so much easier. 🎉 |
I hope so! One piece that's still pretty tricky is mathjax (because you have to support async rendering, and any bbox calls need to look for the mathjax group), and I'll try to address that later. In the meantime, we should make a practice of including |
Started out as a bugfix but kind of turned into a spring cleaning...
In an effort to fix bounding box calculations in IE (one part of #1782) - which were broken by trying to use the
innerHTML
property of an SVG element - I wound up going through every user ofDrawing.bBox
and trying to make sure it was a<text>
or.*-math-group
node that had previously hadsvgTextUtils.convertToTspans
called on it. Because if so, I could use thedata-unformatted
attribute, along with whether or not the node was converted to MathJax, in place of theinnerHTML
in the cache key, which is also much smaller than theinnerHTML
, reducing my memory concerns about this cache.But then while doing THAT it became clear that all the monkey business using
tspan.line
elements to count lines and to properly align multi-line text was a disaster, and not done properly in a lot of places, so I centralized all that in two new utilities:svgTextUtils.positionText
- to be used instead ofDrawing.setPosition
, handles multiline alignment, and also gets called withinconvertToTspans
so if you usetransform
instead of thex
andy
attributes to position the text, you never even need to use this at all.svgTextUtils.lineCount
- nuff said. No more hacky and/or broken selectors for counting lines of text.So after that, almost nobody outside of
svgTextUtils
even needs to know about<tspan>
or the.line
class.Along the way I found a few places we were not supporting or had bugs supporting pseudo-HTML but easily could, and added it: rangeselectors, updatemenus, sliders, carpet axes (titles and tick labels). And in cases that currently break with MathJax I explicitly cancel it (with the
data-notex
attribute). (MathJax test mocks coming in a future PR)And while I was working in
test_syntax.js
I took care of #1773 as well.There are still a few
Drawing.bBox
users that call it on elements larger than a<text>
or MathJax group node, that at this point in this branch we will NOT cache at all. For 🐎 I need to either convert them to their base nodes or find an alternate way to give them a robust cache key: